// Author: ALuoggg
// Artifact: This source code is an incomplete encapsulation of the sqlite C API
// Contact Information
// -- E-mail: 1472190350@qq.com,
// -- Bilibili user_id: 496381358

// lib
const std = @import("std");
const debug = std.debug;
const testing = std.testing;
const fmt = std.fmt;
const Sqlite3 = @This();
const c = @cImport({
    @cInclude("sqlite3.h");
});
// fileds
p_db: *c.sqlite3,
// C API
pub const CAPI = c;
// static const
pub const OK: i32 = c.SQLITE_OK;
pub const DONE: i32 = c.SQLITE_DONE;
pub const ROW: i32 = c.SQLITE_ROW;
// type
pub const ColumnType: type = enum {
    text,
    int64,
    // function
    fn getZigType(self: @This()) type {
        return switch (self) {
            .text => [:0]const u8,
            .int64 => i64,
        };
    }
    pub fn @"'[]const Self' to tuple{ZigType...}"(slice: []const ColumnType) type {
        return @Type(.{ .Struct = .{
            .layout = .auto,
            .fields = fields: {
                var fields: [slice.len]std.builtin.Type.StructField = undefined;
                inline for (&fields, slice, 0..) |*e0, e1, i| {
                    e0.* = .{
                        .name = fmt.comptimePrint("{}", .{i}),
                        .type = e1.getZigType(),
                        .default_value = null,
                        .is_comptime = false,
                        .alignment = 0,
                    };
                }
                break :fields fields[0..];
            },
            .decls = &.{},
            .is_tuple = true,
        } });
    }
};
pub inline fn init_stmt(
    self: *@This(),
    comptime column_types: []const ColumnType,
    sql_str_z: [:0]const u8,
) @This().Stmt(column_types) { // <><>
    const stmt: @This().Stmt(column_types) = @This().Stmt(column_types).init(self, sql_str_z); // <+><+>
    errdefer stmt.deinit();
    return stmt;
}

pub const StepErrorResult: type = error{TestUnexpectedResult};

pub fn Stmt(column_types: []const ColumnType) type {
    return struct {
        // fileds
        comptime Column_TupleType: type = ColumnType.@"'[]const Self' to tuple{ZigType...}"(column_types),
        p_stmt: *allowzero c.sqlite3_stmt,
        // type
        pub const BindType: type = enum {
            null,
            text,
            int64,
            // funcion
            fn getZigType(self: @This()) type {
                return switch (self) {
                    .null => void,
                    .text => [:0]const u8,
                    .int64 => i64,
                };
            }
            pub fn @"'[]const Self' to tuple{ZigType...}"(slice: []const BindType) type {
                return (@Type(.{ .Struct = .{
                    .layout = .auto,
                    .backing_integer = null,
                    .fields = fields: {
                        var fields: [slice.len]std.builtin.Type.StructField = undefined;
                        inline for (&fields, slice, 0..) |*e0, e1, i| {
                            const e0_type: type = e1.getZigType();
                            e0.* = .{
                                .name = fmt.comptimePrint("{}", .{i}).*[0..],
                                .type = e0_type,
                                .default_value = null,
                                .is_comptime = false,
                                .alignment = 8,
                            };
                        }
                        break :fields fields[0..];
                    },
                    .decls = &.{},
                    .is_tuple = true,
                } }));
            }
        };
        // function
        pub inline fn init(
            db: *Sqlite3,
            z_sql: [:0]const u8,
        ) @This() {
            var p_stmt: *allowzero c.sqlite3_stmt = undefined;
            const ret: i32 = c.sqlite3_prepare_v2(db.p_db, z_sql, @intCast(z_sql.len), &p_stmt, null); // <><> init p_stmt // <><> deinit_in(Self.deinit)
            if (ret != Sqlite3.OK) {
                debug.print("初始化sql语句失败, error code:{}", .{ret});
                @panic("nb666");
            }
            errdefer _ = c.sqlite3_finalize(p_stmt); // <><> errdefer deinit p_stmt
            return @This(){ .p_stmt = p_stmt };
            // <><> move p_stmt
        }
        pub inline fn deinit(self: *@This()) void {
            debug.assert(c.sqlite3_finalize(self.p_stmt) == Sqlite3.OK); // <><> deinit self.p_stmt // <><> init_in(Self.init_prepare)
            return void{};
        }
        /// args: Be borrowed until [fn:C_step] is called
        pub inline fn bind(
            self: *@This(),
            idx: i32,
            comptime arg_type: BindType,
            value: arg_type.getZigType(), // <><> init // <><> borrow until deinit_step is called
        ) !void {
            return switch (arg_type) {
                .null => try testing.expect(c.sqlite3_bind_null(self.p_stmt, idx) == Sqlite3.OK),
                .text => try testing.expect(c.sqlite3_bind_text(self.p_stmt, idx, value.ptr, @intCast(value.len), c.SQLITE_STATIC) == Sqlite3.OK),
                .int64 => try testing.expect(c.sqlite3_bind_int64(self.p_stmt, idx, value) == Sqlite3.OK),
            };
        }
        pub inline fn init_step(self: *@This()) i32 {
            const ret: i32 = c.sqlite3_step(self.p_stmt); // <><> init self.p_stmt // <><> deinit_in(Self.deinit_step)
            return (ret);
        }
        pub inline fn deinit_step(self: *@This()) void {
            debug.assert(c.sqlite3_reset(self.p_stmt) == Sqlite3.OK); // <><> deinit self // init_in(Self.init_step)
            return void{};
        }
        pub inline fn column(self: *@This(), comptime column_type: ColumnType, i_col: i32) column_type.getZigType() {
            return switch (column_type) {
                .int64 => c.sqlite3_column_int64(self.p_stmt, i_col),
                .text => text: {
                    const ptr: [*]const u8 = c.sqlite3_column_text(self.p_stmt, i_col);
                    const len: i32 = c.sqlite3_column_bytes(self.p_stmt, i_col);
                    const text: [:0]const u8 = ptr[0..@intCast(len) :0];
                    break :text text;
                },
            };
        }
        fn StepCallbackType(Column_TupleType: type, CallbackOtherArgType: type) type {
            const FnType = *const fn (Column_TupleType, CallbackOtherArgType) bool;
            const v: FnType = struct {
                fn v(_: Column_TupleType, _: CallbackOtherArgType) bool {
                    return true;
                }
            }.v;
            return struct {
                v: FnType = v,
            };
        }
        /// 此函数是对 .{[Fn:bind], [Fn:init_step], [Fn:deinit_step], [Fn:column]} 函数集合的封装, 避免每次编译sql语句后的大量用于执行sql的代码
        pub inline fn step(
            self: *@This(),
            comptime bind_types: []const BindType,
            bind_values: BindType.@"'[]const Self' to tuple{ZigType...}"(bind_types),
            callback_other_arg: anytype,
            callback: StepCallbackType(self.Column_TupleType, @TypeOf(callback_other_arg)),
        ) StepErrorResult!void {
            var ret: i32 = undefined;
            // bind args
            inline for (bind_types, bind_values, 1..) |bind_type, bind_value, i| { // <><> borrow bind_values until deinit_step is called
                try self.bind(i, bind_type, bind_value);
            }
            // step
            while (blk: {
                ret = self.init_step();
                break :blk ret == Sqlite3.ROW;
            }) { // <><> init
                // column
                var callback_arg: self.Column_TupleType = undefined;
                inline for (&callback_arg, column_types, 0..) |*e0, e1, i| {
                    e0.* = self.column(e1, i);
                }
                if (callback.v(callback_arg, callback_other_arg)) break;
            }
            try testing.expect(ret == Sqlite3.DONE or ret == Sqlite3.ROW);
            defer self.deinit_step(); // <><> deinit // <><> back bind_value
            return void{};
        }
    };
}
// function
pub inline fn init(filename: [:0]const u8) struct { i32, Sqlite3 } {
    var ret: i32 = undefined;
    var self: @This() = .{ // <><> open // <><> drop_in(Self.C_close)
        .p_db = p_db: {
            var p_db: ?*c.sqlite3 = undefined;
            ret = c.sqlite3_open(filename, &p_db); // <><> open p_db
            errdefer _ = c.sqlite3_close(p_db); // <><> errdefer drop p_db

            break :p_db p_db.?;
            // <>><> move p_db
        },
    };
    errdefer _ = self.C_close(); // <><> errdrop self.p_db
    return .{ ret, self };
    // <><> move self
}
pub inline fn deinit(self: *@This()) void {
    const rst: i32 = c.sqlite3_close(self.p_db);
    if (rst != Sqlite3.OK) @panic("nb"); // <><> deinit self.p_db // <><> init_in(Self.O_open)
    return void{};
}
pub inline fn exec(
    self: *@This(),
    sql: [*:0]const u8,
    callback: ?*const fn (?*anyopaque, i32, [*c][*c]u8, [*c][*c]u8) callconv(.C) i32,
    callback_arg: ?*void,
    errmsg: [*c][*c]u8,
) i32 {
    // 空注释, 用于占行
    return c.sqlite3_exec(self.p_db, sql, callback, callback_arg, errmsg);
}
